﻿using CleanArchitecture.Core.Configuration;
using CleanArchitecture.Core.Entities;
using CleanArchitecture.Core.Handlers;
using CleanArchitecture.Core.Interfaces;
using CleanArchitecture.Infrastructure.Entities.Settings;
using CleanArchitecture.Infrastructure.Interfaces;
using CleanArchitecture.Infrastructure.Interfaces.SystemLog;
using System;
using System.Collections.Generic;
using System.Linq;

namespace CleanArchitecture.Infrastructure.Services.SystemLog
{
    /// <summary>
    /// Default logger
    /// </summary>
    public partial class DbSystemLogService : ISystemLogService
    {
        #region Fields

        private readonly CommonSettings _commonSettings;
        private readonly IDbContext _dbContext;
        private readonly IRepository<SystemLogEntity> _logRepository;
        private readonly IWebHelper _webHelper;

        #endregion

        #region Ctor

        public DbSystemLogService(CommonSettings commonSettings,
            IDbContext dbContext,
            IRepository<SystemLogEntity> logRepository,
            IWebHelper webHelper)
        {
            this._commonSettings = commonSettings;
            this._dbContext = dbContext;
            this._logRepository = logRepository;
            this._webHelper = webHelper;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Determines whether a log level is enabled
        /// DavidLee 2019/11/18 更改日志写入方式： 根据sys_setting表中
        /// CommonSettings.IgnoreLogWordlist 设置的忽略写入日志级别，在插入日志时别特定级别的日志不写入数据库
        /// </summary>
        /// <param name="level">Log level</param>
        /// <returns>Result</returns>
        public virtual bool IsEnabled(SystemLogLevel level)
        {
            if (!_commonSettings.IgnoreLogWordlist.Any())
                return true;

            return !_commonSettings
                .IgnoreLogWordlist
                .Any(x => level.ToString("F").Equals(x, StringComparison.InvariantCultureIgnoreCase));
        }

        /// <summary>
        /// Deletes a log item
        /// </summary>
        /// <param name="log">Log item</param>
        public virtual void DeleteLog(SystemLogEntity log)
        {
            if (log == null)
                throw new ArgumentNullException(nameof(log));

            _logRepository.Delete(log);
        }

        /// <summary>
        /// Deletes a log items
        /// </summary>
        /// <param name="logs">Log items</param>
        public virtual void DeleteLogs(IList<SystemLogEntity> logs)
        {
            if (logs == null)
                throw new ArgumentNullException(nameof(logs));

            _logRepository.Delete(logs);
        }

        /// <summary>
        /// Clears a log
        /// </summary>
        public virtual void ClearLog()
        {
            //do all databases support "Truncate command"?
            var logTableName = _dbContext.GetTableName<SystemLogEntity>();
            _dbContext.ExecuteSqlCommand($"TRUNCATE TABLE {logTableName}");
        }

        /// <summary>
        /// Gets all log items
        /// </summary>
        /// <param name="fromUtc">Log item creation from; null to load all records</param>
        /// <param name="toUtc">Log item creation to; null to load all records</param>
        /// <param name="message">Message</param>
        /// <param name="logLevel">Log level; null to load all records</param>
        /// <param name="pageIndex">Page index</param>
        /// <param name="pageSize">Page size</param>
        /// <returns>Log item items</returns>
        public virtual IViewPagedList<SystemLogEntity> GetAllLogs(DateTime? fromUtc = null, DateTime? toUtc = null,
            string message = "", SystemLogLevel? logLevel = null,
            int pageIndex = 0, int pageSize = int.MaxValue)
        {
            var query = _logRepository.Table;
            if (fromUtc.HasValue)
                query = query.Where(l => fromUtc.Value <= l.CreatedOnTimeUtc);
            if (toUtc.HasValue)
                query = query.Where(l => toUtc.Value >= l.CreatedOnTimeUtc);
            if (logLevel.HasValue)
            {
                var logLevelStr = logLevel.Value.ToString("F");
                query = query.Where(l => string.Equals(logLevelStr, l.LogLevel, StringComparison.CurrentCultureIgnoreCase));
            }

            if (!string.IsNullOrEmpty(message))
                query = query.Where(l => l.LogMessage.Contains(message));
            query = query.OrderByDescending(l => l.CreatedOnTimeUtc);

            var log = new ViewPagedList<SystemLogEntity>(query, pageIndex, pageSize);
            return log;
        }

        /// <summary>
        /// Gets a log item
        /// </summary>
        /// <param name="logId">Log item identifier</param>
        /// <returns>Log item</returns>
        public virtual SystemLogEntity GetLogById(int logId)
        {
            if (logId == 0)
                return null;

            return _logRepository.GetById(logId);
        }

        /// <summary>
        /// Get log items by identifiers
        /// </summary>
        /// <param name="logIds">Log item identifiers</param>
        /// <returns>Log items</returns>
        public virtual IList<SystemLogEntity> GetLogByIds(int[] logIds)
        {
            if (logIds == null || logIds.Length == 0)
                return new List<SystemLogEntity>();

            var query = from l in _logRepository.Table
                        where logIds.Contains(l.Id)
                        select l;
            var logItems = query.ToList();
            //sort by passed identifiers
            var sortedLogItems = new List<SystemLogEntity>();
            foreach (var id in logIds)
            {
                var log = logItems.Find(x => x.Id == id);
                if (log != null)
                    sortedLogItems.Add(log);
            }

            return sortedLogItems;
        }

        /// <summary>
        /// Inserts a log item
        /// </summary>
        /// <param name="logLevel">Log level</param>
        /// <param name="logMessage">The short message</param>
        public virtual SystemLogEntity InsertLog(SystemLogLevel logLevel, string logMessage)
        {
            var log = new SystemLogEntity
            {
                LogLevel = logLevel.ToString("F"),
                LogMessage = logMessage,
                IpAddress = _webHelper.GetCurrentIpAddress(),
                PageUrl = _webHelper.GetThisPageUrl(true, true),
                CreatedOnTimeUtc = DateTime.UtcNow
            };
            _logRepository.Insert(log);

            return log;
        }

        /// <summary>
        /// Information
        /// </summary>
        /// <param name="message">Message</param>
        /// <param name="exception">Exception</param>
        public virtual void Information(string message, Exception exception = null)
        {
            //don't log thread abort exception
            if (exception is System.Threading.ThreadAbortException)
                return;

            string logMsg = message + " With Exception Message: " + exception?.ToString() ?? "No Exception.";
            if (IsEnabled(SystemLogLevel.Information))
                InsertLog(SystemLogLevel.Information, logMsg);
        }

        /// <summary>
        /// Warning
        /// </summary>
        /// <param name="message">Message</param>
        /// <param name="exception">Exception</param>
        public virtual void Warning(string message, Exception exception = null)
        {
            //don't log thread abort exception
            if (exception is System.Threading.ThreadAbortException)
                return;

            string logMsg = message + " With Exception Message: " + exception?.ToString() ?? "No Exception.";
            if (IsEnabled(SystemLogLevel.Warning))
                InsertLog(SystemLogLevel.Warning, logMsg);
        }

        /// <summary>
        /// Error
        /// </summary>
        /// <param name="message">Message</param>
        /// <param name="exception">Exception</param>
        public virtual void Error(string message, Exception exception = null)
        {
            //don't log thread abort exception
            if (exception is System.Threading.ThreadAbortException)
                return;

            string logMsg = message + " With Exception Message: " + exception?.ToString() ?? "No Exception.";
            if (IsEnabled(SystemLogLevel.Error))
                InsertLog(SystemLogLevel.Error, logMsg);
        }

        /// <summary>
        /// Debug
        /// </summary>
        /// <param name="message">Message</param>
        /// <param name="exception">Exception</param>
        public virtual void Debug(string message, Exception exception = null)
        {
            //don't log thread abort exception
            if (exception is System.Threading.ThreadAbortException)
                return;

            string logMsg = message + " With Exception Message: " + exception?.ToString() ?? "No Exception.";
            if (IsEnabled(SystemLogLevel.Debug))
                InsertLog(SystemLogLevel.Debug, logMsg);
        }

        #endregion
    }
}